import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import random
from sklearn import datasets, linear_model
from sklearn.metrics import accuracy_score

# 显示原数据
# names =['x','y','class']
dataset = pd.read_csv("dataset_circles.csv")
x = dataset.iloc[:, 0]
y = dataset.iloc[:, 1]
z = dataset.iloc[:, 2]
x = x.tolist()
y = y.tolist()
z = z.tolist()
x1 = np.array(x)
y1 = np.array(y)
z1 = np.array(z)
# 绘制原始样本点
plt.scatter(x1, y1, c=z1)
plt.title('original dataset')
plt.xlabel('x')
plt.ylabel('y')
plt.show()

x = x1.reshape([-1, 1])
y = y1.reshape([-1, 1])
xy = np.concatenate((x, y), axis=1)

t = np.zeros((xy.shape[0], 2))  # 为什么要两列？
t[np.where(z1 == 0), 0] = 1
t[np.where(z1 == 1), 1] = 1


# define sigmod
def sigmoid(X):
    return 1.0 / (1 + np.exp(-X))


# generate the NN model
class NN_Model:
    def __init__(self, nodes=None):
        self.epsilon = 0.01  # learning rate
        self.n_epoch = 1000  # iterative number

        if not nodes:
            self.nodes = [2, 8, 2]  # default nodes size (from input -> output)
        else:
            self.nodes = nodes

    def init_weight(self):
        W = []
        B = []

        n_layer = len(self.nodes)
        for i in range(n_layer - 1):
            w = np.random.randn(self.nodes[i], self.nodes[i + 1]) / np.sqrt(self.nodes[i])
            b = np.random.randn(1, self.nodes[i + 1])

            W.append(w)
            B.append(b)

        self.W = W
        self.B = B

    def forward(self, X):
        Z = []
        x0 = X
        for i in range(len(self.nodes) - 1):
            z = sigmoid(np.dot(x0, self.W[i]) + self.B[i])
            # print('zshape',np.shape(z))# 399*(8,7,2)
            x0 = z

            Z.append(z)

        self.Z = Z
        return Z[-1], Z

    # back-propagation
    def backpropagation(self, X, y, n_epoch=None, epsilon=None):
        if not n_epoch: n_epoch = self.n_epoch
        if not epsilon: epsilon = self.epsilon

        self.X = X
        self.Y = y
        # print('yshape',np.shape(self.Y)) #yshape (399, 2)
        for i in range(n_epoch):
            # forward to calculate each node's output
            self.forward(X)

            self.epoch = i
            self.evaluate()

            # calc weights update
            W = self.W
            B = self.B
            Z = self.Z

            D = []
            d0 = y
            n_layer = len(self.nodes)
            for j in range(n_layer - 1, 0, -1):
                jj = j - 1
                z = self.Z[jj]

                if j == n_layer - 1:
                    d = z * (1 - z) * (d0 - z)
                else:
                    d = z * (1 - z) * np.dot(d0, W[j].T)

                d0 = d
                # print('d0shape',np.shape(d0)) #399*(2,7,8,2)
                D.insert(0, d)

            # update weights
            for j in range(n_layer - 1, 0, -1):
                jj = j - 1

                if jj != 0:
                    W[jj] += epsilon * np.dot(Z[jj - 1].T, D[jj])
                else:
                    W[jj] += epsilon * np.dot(X.T, D[jj])

                B[jj] += epsilon * np.sum(D[jj], axis=0)
        return D, W, B

    def evaluate(self):
        z = self.Z[-1]

        # print loss, accuracy
        L = np.sum((z - self.Y) ** 2)

        y_pred = np.argmax(z, axis=1)
        y_true = np.argmax(self.Y, axis=1)
        acc = accuracy_score(y_true, y_pred)

        if self.epoch % 100 == 0:
            print("L = %f, acc = %f" % (L, acc))


# use the NN model and training
nn = NN_Model([2, 8, 7, 2])
nn.init_weight()
D, W, B = nn.backpropagation(xy, t, 2000)
# predict results & plot results
y_res, Z = nn.forward(xy)
y_pred = np.argmax(y_res, axis=1)

plt.scatter(xy[:, 0], xy[:, 1], c=y_pred, cmap=plt.cm.Spectral)
plt.title("predicted")
plt.show()